package com.audaque.springboot.foshanupload.redis.aspect;


import com.audaque.springboot.foshanupload.core.exception.FastFailException;
import com.audaque.springboot.foshanupload.core.exception.RetryMaxException;
import com.audaque.springboot.foshanupload.redis.anno.RedisLuaBucketRateLimterAnno;
import com.audaque.springboot.foshanupload.redis.component.StringRedisTemplateRetry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

/**
 * @author wangyan01
 */
@Slf4j
@Aspect
@Component
public class RedisLuaBucketRateLimterAnnoAspect {

    /**
     * 注入redis操作类
     */
    @Autowired
    private StringRedisTemplateRetry stringRedisTemplateRetry;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private DefaultRedisScript<List> redisScript;

    @Value("${spring.application.name}")
    private String appName;


    /**
     * 初始化 redisScript 类
     * 返回值为 List
     */
    @PostConstruct
    public void init() {
        redisScript = new DefaultRedisScript<List>();
        redisScript.setResultType(List.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/redisLuaBucketRateLimterAnno.lua")));
    }

    @Around("@annotation(redisLuaBucketRateLimterAnno)")
    public Object around(ProceedingJoinPoint point, RedisLuaBucketRateLimterAnno redisLuaBucketRateLimterAnno) throws Throwable {

        Signature signature = point.getSignature();
        Object target = point.getTarget();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("注解未作用在方法上!");
        }


        String name = redisLuaBucketRateLimterAnno.name();
        String key = name;
        if (StringUtils.isBlank(name)) {
            key = getKey(signature, target);
        }

        String msg = redisLuaBucketRateLimterAnno.msg();
        Boolean retryYn = redisLuaBucketRateLimterAnno.retryYn();

        //拼接记录上次请求时间的key
        String lastMillRequestKey = key + "_" + redisLuaBucketRateLimterAnno.last_mill_request_key();
        //初始化令牌桶的容量
        long limit = redisLuaBucketRateLimterAnno.limit();
        //请求令牌的数量 每次请求消耗令牌的数量
        long permits = redisLuaBucketRateLimterAnno.permits();
        //每秒放入桶中的令牌数 即流速
        long rate = redisLuaBucketRateLimterAnno.rate();
        //当前系统时间
        long currMillTime = System.currentTimeMillis() / 1000;
        //拼接lua脚本参数
        List<String> keys = Arrays.asList(key, lastMillRequestKey);

        if (retryYn) {
            //调用lua脚本并执行
            this.retryUntilSuccess(redisScript, keys, limit, permits, rate, currMillTime);
        } else {
            //lua 脚本返回 "0" 表示超出流量大小，返回1表示没有超出流量大小
            //调用lua脚本并执行
            List result = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limit), String.valueOf(permits), String.valueOf(rate), String.valueOf(currMillTime));
            result = ListUtils.emptyIfNull(result);
            if (result.isEmpty()) {
                throw new Exception("redis脚本执行结果为空数组");
            }
            Object o = result.get(0);
            if (o == null) {
                throw new Exception("redis脚本执行结果首元素为null");
            }
            String s = o.toString();
            //lua 脚本返回 "0" 表示超出流量大小，返回1表示没有超出流量大小
            if (StringUtils.equals(s, "0")) {
                throw new FastFailException(msg);
            }

        }


        // 没有限流，直接放行
        return point.proceed();
    }


    private void retryUntilSuccess(RedisScript<List> script, List<String> keys, long limit, long permits, long rate, long currMillTime) {
        try {
            //调用lua脚本并执行
            stringRedisTemplateRetry.execute(redisScript, keys, String.valueOf(limit), String.valueOf(permits), String.valueOf(rate), String.valueOf(currMillTime));
        } catch (RetryMaxException e) {
            log.debug("lua脚本,重试次数达到上限，继续重试");
            retryUntilSuccess(redisScript, keys, limit, permits, rate, currMillTime);
        }
    }


    private String getKey(Signature signature, Object target) {
        try {
            //获取类路径
            String className = target.getClass().getName();
            //获取方法名
            String methodName = signature.getName();
            //获取服务器节点ip
            String serverAddress = InetAddress.getLocalHost().getHostAddress();
            //拼接 服务名称 + 类路径 + 方法名 + 服务器节点ip 为唯一key
            return appName + ":" + className + "." + methodName + "_" + serverAddress;
        } catch (UnknownHostException e) {
            log.error("获取节点ip失败");
        }
        return null;
    }
}
